#ifndef _OSG_PTR_HELPERS_H_
#define _OSG_PTR_HELPERS_H_

#include <OpenSG/OSGFieldContainerPtr.h>
#include <OpenSG/OSGAttachmentContainerPtr.h>
#include <OpenSG/OSGAttachmentPtr.h>
#include <OpenSG/OSGRefPtr.h>
#include <OpenSG/OSGNodePtr.h>
#include <boost/python.hpp>
//#include <boost/python/converter/shared_ptr_deleter.hpp>
#include <typeinfo>

namespace bp = boost::python;

/** @name Meta functions for boost.python.
*
* These routines help define the methods needed for
* registering and handling the OpenSG smart pointer types.
*
* Our goal is to make everything be held by RefPtrs and to make
* it possible to auto-convert to and from those ref_ptrs as needed.
*
*
* bp::register_ptr_to_python< osg::ClassPtr >();
* bp::implicitly_convertible< osg::ClassRefPtr, osg::ClassPtr >();
* bp::implicitly_convertible< osg::ClassPtr, osg::ParentPtr >();
*
* bp::def("RefPtr", &pyopensg::ToRefPtr<osg::ClassPtr>);
* bp::def("FCPtr", &pyopensg::ToFcPtr<osg::ClassPtr>);
*
* See register_ptr_to_python, pointee<>
*
* We must register a pointee metfunction and a get_pointer helper function
* for each smart pointer type we intend to use for storing and passing
* object references.
*
* See to_python_converter
*
*
*
*/
//@{
namespace boost { namespace python
{

/** Need all these specializations because OpenSG has a pretty complex field pointer hierarchy. */


//typename RefPtrType::FCPtrType

template<class FC_PTR_TYPE>
struct pointee< OSG::RefPtr< FC_PTR_TYPE > >
{
   typedef typename FC_PTR_TYPE::StoredObjectType type;
};

template<class FC_PTR_TYPE>
inline typename OSG::RefPtr<FC_PTR_TYPE>::StoredObjectType*
   get_pointer(OSG::RefPtr< FC_PTR_TYPE > const& p)
{
   return p.get().getCPtr();
}


/*
  template<typename B, typename C> struct pointee< osg::FCPtr<B,C> >
  {
     typedef typename OSG::FCPtr<B,C>::StoredObjectType type;
  };


  template <class B, class C>
  inline typename osg::FCPtr<B,C>::StoredObjectType*
     get_pointer(OSG::FCPtr<B,C> const& p)
  { return p.getCPtr(); }

  template<class B, class C> struct pointee< osg::RefPtr<osg::FCPtr<B,C> > >
  {
     typedef typename OSG::FCPtr<B,C>::StoredObjectType type;
  };

  template<class B, class C>
  inline typename osg::FCPtr<B,C>::StoredObjectType*
     get_pointer(osg::RefPtr<osg::FCPtr<B,C> > const & p)
  {
      return p.get().getCPtr();
  }


  // AttachmentPtr
  template<> struct pointee< osg::AttachmentPtr >
  {
     typedef osg::AttachmentPtr::StoredObjectType type;
  };

  inline osg::AttachmentPtr::StoredObjectType* get_pointer(osg::AttachmentPtr const& p)
  { return p.getCPtr(); }

  template<> struct pointee< osg::AttachmentRefPtr >
  {
     typedef osg::AttachmentPtr::StoredObjectType type;
  };

  inline osg::AttachmentPtr::StoredObjectType* get_pointer(osg::AttachmentRefPtr const & p)
  {
      return p.get().getCPtr();
  }

  // AttachmentContainerPtr
  template<> struct pointee< osg::AttachmentContainerPtr >
  {
     typedef osg::AttachmentContainerPtr::StoredObjectType type;
  };

  inline osg::AttachmentContainerPtr::StoredObjectType* get_pointer(osg::AttachmentContainerPtr const& p)
  { return p.getCPtr(); }

  template<> struct pointee< osg::AttachmentContainerRefPtr >
  {
     typedef osg::AttachmentContainerPtr::StoredObjectType type;
  };

  inline osg::AttachmentContainerPtr::StoredObjectType* get_pointer(osg::AttachmentContainerRefPtr const & p)
  {
      return p.get().getCPtr();
  }

  // Node
  template<> struct pointee<osg::NodePtr>
  {
     typedef osg::NodePtr::StoredObjectType type;
  };

  inline osg::Node* get_pointer(OSG::NodePtr const & p)
  {
      return p.getCPtr();
  }

  template <> struct pointee<osg::NodeRefPtr>
  {
     typedef osg::NodePtr::StoredObjectType type;
  };

  inline osg::Node* get_pointer(osg::NodeRefPtr const & p)
  {
      return p.get().getCPtr();
  }
  */
}}
//@}

/** @name Ptr conversion methods
*
* See to_python_converter
*
* Register conversion that takes an FCPtr and converts it to a RefPtr so we
* can actually use it.
*
* pyopensg::register_fcptr_to_python<ref_ptr_type>::execute();
*/
//@{
namespace pyopensg
{

/** For a given Ptr type, create an object with the refptr type, and return that object. */

/*  This idea is something that looks like this.
*
* to_python_converter<ptr<T>, ptr_to_python<T> >();
*
template <class T>
struct ptr_to_python
{
   static PyObject *convert(ptr<T> const &p)
   { return Py_INCREF(object(ref_ptr<T>(p)).ptr()); }
};
*/

template <class RefPtrType>
struct fcptr_to_python
{
   static PyObject *convert(typename RefPtrType::FCPtrType const& fcPtr)
   {
      return bp::incref(bp::object(RefPtrType(fcPtr)).ptr());
   }
};

template <class RefPtrType>
struct register_fcptr_to_python
{
   static void execute()
   {
      bp::to_python_converter<typename RefPtrType::FCPtrType, fcptr_to_python<RefPtrType> >();
   }
};

// Template class to register from_python converter for fcptr types.
//   This is needed atleast for conversion from None.
//
// Base on shared_ptr_from_python in converter/shared_ptr_from_python.hpp
template<typename FCPtrType>
struct fcptr_from_python
{
   typedef typename FCPtrType::StoredObjectType Pointee;   // The type we point to

   fcptr_from_python()
   {
      bp::converter::registry::insert(&convertible, &construct, bp::type_id<FCPtrType>());
   }

private:

   static void* convertible(PyObject* p)
   {
      // Always produce a pointer from none
      if (Py_None == p)
      { return p; }
      else
      { return NULL; }

      // Otherwise, we can do it if we can get the pointer out
      // XXX: Not sure if all of this is right.
      /*
      void* result = bp::converter::get_lvalue_from_python(p,
                                             bp::converter::registered<Pointee>::converters);
      return result;
      */
   }

   static void construct(PyObject* source,
                         bp::converter::rvalue_from_python_stage1_data* data)
   {
     void* const storage
        = ((bp::converter::rvalue_from_python_storage<FCPtrType>*)data)->storage.bytes;

     // Deal with the "None" case.
     if (data->convertible == source)
     {
        new (storage) FCPtrType();
     }
     else
     {
        // Shouldn't ever happen.  We can't convert like this from openSG
        // new (storage) FCPtrType(static_cast<Pointee*>(data->convertible));

        assert(false && "Tried to convert a raw OpenSG fc * to a fc_ptr type.");

        new (storage) FCPtrType();
     }

     data->convertible = storage;
   }

};

// Helper for registering an fcptr type
template <class RefPtrType>
struct register_fcptr
{
   static void execute()
   {
      register_fcptr_to_python<RefPtrType>::execute();
      fcptr_from_python<typename RefPtrType::FCPtrType>();
   }
};


// Compare and has two RefPtrs
template <class RefPtrType>
static int cmp_ref_ptrs(const RefPtrType& left, const RefPtrType& right)
{
   //          10   - 20  = -10
   int diff = int(left.get().getFieldContainerId())-int(right.get().getFieldContainerId());
   return diff;
}

template <class RefPtrType>
static int hash_ref_ptr(const RefPtrType& ref)
{
   return ref.get().getFieldContainerId();
}

}
//@}

namespace pyopensg
{
//
// Helper templates for going to/from FCPtrs and ref ptrs
template<typename FCPTR>
osg::RefPtr<FCPTR> ToRefPtr(FCPTR& ptr)
{
   return osg::RefPtr<FCPTR>(ptr);
}

template<typename FCPTR>
FCPTR ToFcPtr(osg::RefPtr<FCPTR>& ptr)
{
   return ptr.get();
}

}

#endif

